package sebastiankerckhof.scene.geometry;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;

import sebastiankerckhof.math.Matrix4f;
import sebastiankerckhof.math.Point3f;
import sebastiankerckhof.math.Vector3f;
import sebastiankerckhof.render.raytrace.Ray;
import sebastiankerckhof.render.raytrace.Hit.Hit;
import sebastiankerckhof.render.raytrace.Hit.TriangleHit;
import sebastiankerckhof.scene.material.Material;
import sebastiankerckhof.scene.texture.TexCoord2f;
import sebastiankerckhof.scene.texture.Texture;
import sebastiankerckhof.scene.transformation.TransformationStack;




public class Triangle implements Surface{
	public Vertex a;
	public Vertex b;
	public Vertex c;
	
	public BBox bbox;
	
	public Mesh mesh;
	
	public Triangle(){}
	
	public Triangle(Vertex a, Vertex b, Vertex c){
		this.a = a;
		this.b = b;
		this.c = c;
	}
	
	public Triangle(Vertex[] v){
		this.a = v[0];
		this.b = v[1];
		this.c = v[2];
	}
	
	public float xMin(){
		float val;
		if(a.position.x < b.position.x && a.position.x < c.position.x)
			val = a.position.x;
		else if(b.position.x < c.position.x)
			val = b.position.x;
		else
			val = c.position.x;
		
		return val;
	}
	
	public float xMax(){
		float val;
		if(a.position.x > b.position.x && a.position.x > c.position.x)
			val =  a.position.x;
		else if(b.position.x > c.position.x)
			val = b.position.x;
		else
			val = c.position.x;
		
		return val;
	}
	
	public float yMin(){
		float val;
		if(a.position.y < b.position.y && a.position.y < c.position.y)
			val = a.position.y;
		else if(b.position.y < c.position.y)
			val = b.position.y;
		else
			val = c.position.y;
		
		return val;
	}
	
	public float yMax(){
		float val;
		if(a.position.y > b.position.y && a.position.y > c.position.y)
			val = a.position.y;
		else if(b.position.y > c.position.y)
			val = b.position.y;
		else
			val = c.position.y;
		
		return val;
	}

	
	public float zMin(){
		float val;
		if(a.position.z < b.position.z && a.position.z < c.position.z)
			val = a.position.z;
		else if(b.position.z < c.position.z)
			val = b.position.z;
		else
			val = c.position.z;
		
		return val;
	}
	
	public float zMax(){
		float val;
		if(a.position.z > b.position.z && a.position.z > c.position.z)
			val = a.position.z;
		else if(b.position.z > c.position.z)
			val = b.position.z;
		else
			val = c.position.z;
		
		return val;
	}

	@Override
  public boolean hit(Ray r, float t0, Hit hit){
  	float a = this.a.position.x - this.b.position.x;
  	float b = this.a.position.y - this.b.position.y;
  	float c = this.a.position.z - this.b.position.z;
  	
  	float d = this.a.position.x - this.c.position.x;
  	float e = this.a.position.y - this.c.position.y;
  	float f = this.a.position.z - this.c.position.z;
  	
  	float g = r.direction.x;
  	float h = r.direction.y;
  	float i = r.direction.z;
  	
  	float j = this.a.position.x - r.origin.x;
  	float k = this.a.position.y - r.origin.y;
  	float l = this.a.position.z - r.origin.z;
  	
  	float ei_minus_hf = e*i - h*f;
  	float gf_minus_di = g*f - d*i;
  	float dh_minus_eg = d*h - e*g;
  	
  	float ak_minus_jb = a*k - j*b;
  	float jc_minus_al = j*c - a*l;
  	float bl_minus_kc = b*l - k*c;
  	
  	float M = a*ei_minus_hf + b*gf_minus_di +c*dh_minus_eg;
  	
  	float t = ( f*ak_minus_jb + e*jc_minus_al + d*bl_minus_kc) / M;
  	
  	if(t < 0 || t > hit.distance)
  		return false;
  	
  	float gamma = (i*ak_minus_jb + h*jc_minus_al + g*bl_minus_kc) / M;
  	if(gamma < 0 || gamma > 1)
  		return false;
  	
  	float beta = (j*ei_minus_hf + k*gf_minus_di + l*dh_minus_eg) / M;
  	if(beta < 0 || beta > (1-gamma))
  		return false;
  	
  	hit.surface = this;
  	hit.setType(new TriangleHit(1.0f - beta - gamma, beta, gamma));
  	hit.distance = t;
  	
  	return true;
  	
  }
	
	public Vector3f getNormal(float alpha, float beta, float gamma){
  	Vector3f normal = this.a.normal.scale(1.0f-beta-gamma).add(
											this.b.normal.scale(beta).add(
											this.c.normal.scale(gamma)));

  	return normal.normalize();
	}

	@Override
  public void transform(Matrix4f m, Matrix4f inverse) {
		this.a.transform(m, inverse);
		this.b.transform(m, inverse);
		this.c.transform(m, inverse);
		
		calculateBBox();
  }

	public void calculateBBox(){
		Point3f min = new Point3f(this.xMin(), this.yMin(), this.zMin());
		Point3f max = new Point3f(this.xMax(), this.yMax(), this.zMax());
		this.bbox = new BBox(min,max);
	}
	@Override
  public BBox getBBox() {
		if (this.bbox == null)
			calculateBBox();

		return this.bbox;
  }

	@Override
  public float getMidPoint(int axis) {
	  return (a.position.get(axis) + b.position.get(axis) + c.position.get(axis) / 3f);
  }


	public TexCoord2f getTexCoord(float alpha, float beta, float gamma){
		if(this.getTexture() == null){
  		return null;
  	}
	  float u, v;
		u = alpha * a.texture.x + beta * b.texture.x + gamma * c.texture.x;
		v = alpha * a.texture.y + beta * b.texture.y + gamma * c.texture.y;
		return new TexCoord2f(u, v);
	}
	
	@Override
  public void setMaterial(Material m) {
	  mesh.setMaterial(m);
  }

	@Override
  public void setTexture(Texture t) {
	  mesh.setTexture(t);
  }

	@Override
  public Material getMaterial() {
	  return mesh.getMaterial();
  }

	@Override
  public Texture getTexture() {
	  return mesh.getTexture();
  }

	@Override
  public void setMotion(TransformationStack t) {}

	@Override
  public void transformToStep(float step) {}

	@Override
  public void transformFromStep(float step) {}

}
